﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="SiteColumnHelper.cs" company="Collaboris Ltd.">
//   Copyright (c) Collaboris Ltd. All rights Reserved.
// </copyright>
// <summary>
//   Defines the Site Column Helper type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Collaboris.SharePoint.Wss.Columns
{
    using System;
    using System.Xml;

    using Collaboris.Utils;

    using Microsoft.SharePoint;
    using Collaboris.Sharepoint.WSS.ContentTypes;



    /// <summary>
    /// A utility class for developing with SharePoint Site Columns
    /// </summary>    
    public class SiteColumnHelper : WssHelperBase
    {

        /// <summary>
        /// Creates a new field to web.
        /// </summary>
        /// <param name="web">The web.</param>
        /// <param name="fieldName">Name of the field.</param>
        /// <param name="fieldType">Type of the field.</param>
        /// <param name="fieldRequired">if set to <c>true</c> [field required].</param>
        /// <returns></returns>
        public static string CreateNewFieldInWeb(SPWeb web, string fieldName, SPFieldType fieldType, bool fieldRequired, bool overwrite)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "SPListHelper", "CreateNewFieldInWeb");

            ValidationHelper.VerifyObjectArgument(web, "web");
            ValidationHelper.VerifyStringArgument(fieldName, "fieldName");

            string internalFieldName;

            try
            {
                SPFieldCollection webFields = web.Fields;
                if (webFields.ContainsField(fieldName) && overwrite == false)
                {
                    throw new ApplicationException("Field Name:" + fieldName +
                                                   " already exist in the web:" +
                                                   web.Title);
                }

                if (webFields.ContainsField(fieldName) && overwrite)
                {
                    webFields[fieldName].Delete();
                }

                internalFieldName = webFields.Add(fieldName, fieldType, fieldRequired);
            }
            catch (Exception ex)
            {
                TraceHelper.TraceException(TraceSwitch, "SPListHelper", "CreateNewFieldInWeb", ex);
                throw;
            }
            finally
            {
                TraceHelper.TraceMethodEnd(TraceSwitch, "SPListHelper", "AddFieldToList");
            }

            return internalFieldName;
        }

        /// <summary>
        /// Saves the site column.
        /// </summary>
        /// <param name="web">The web.</param>
        /// <param name="camlDoc">The caml doc.</param>
        /// <param name="removeColumns">if set to <c>true</c> [remove columns].</param>
        /// <param name="stopOnError">if set to <c>true</c> [stop on error].</param>
        /// <returns></returns>
        public static bool SaveSiteColumn(SPWeb web, XmlDocument camlDoc, bool removeColumns, bool stopOnError)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "SiteColumnHelper", "SaveSiteColumn");
            bool success = true;

            ValidationHelper.VerifyObjectArgument(camlDoc, "camlDoc");
            ValidationHelper.VerifyObjectArgument(web, "web");

            // adds the Namespaces required to query CAML docs
            XmlNamespaceManager nsmgr = Utils.CreateCAMLNameSpaceMgr(camlDoc);

            TraceHelper.TraceVerbose(TraceSwitch, "SiteColumnHelper", "Selecting Fields from the CAML Xml Document");

            // get the Content Type Nodes.
            XmlNodeList allElementNodes = camlDoc.SelectNodes("/CAML:Elements/CAML:Field", nsmgr);

            // iterate the Conent Type Nodes
            foreach (XmlNode fieldNode in allElementNodes)
            {
                string fieldId = fieldNode.Attributes["ID"].Value;
                string fieldName = fieldNode.Attributes["Name"].Value;

                if (string.IsNullOrEmpty(fieldId))
                {
                    string errMsg = string.Format("An 'ID' cannot be found from this Xml '{0}'", fieldNode.OuterXml);
                    if (stopOnError)
                    {
                        throw new NullReferenceException(errMsg);
                    }

                    TraceHelper.TraceWarning(TraceSwitch, "SiteColumnHelper", errMsg);
                    continue;
                }

                try
                {
                    // we have a Content Type Id so lets begin to update or add it.
                    UpdateSaveSiteColumn(web, removeColumns, fieldNode, fieldId, fieldName);
                }
                catch (Exception ex)
                {
                    if (stopOnError)
                    {
                        // bubble it back out and let the end of the thread
                        // log the exception.
                        throw;
                    }

                    TraceHelper.TraceException(TraceSwitch, "SiteColumnHelper", "SaveSiteColumn", ex); 
                    success = false;
                }
            }

            TraceHelper.TraceMethodEnd(TraceSwitch, "SiteColumnHelper", "SaveSiteColumn");
            return success;
        }

        /// <summary>
        /// Updates or Saves site column.
        /// </summary>
        /// <param name="web">The web.</param>
        /// <param name="removeColumns">if set to <c>true</c> [remove columns].</param>
        /// <param name="fieldNode">The "Field" node as specified in CAML.</param>
        /// <param name="fieldId">The field id.</param>
        private static void UpdateSaveSiteColumn(SPWeb web, bool removeColumns, XmlNode fieldNode, string fieldId, string fieldName)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "SiteColumnHelper", "UpdateSaveSiteColumn");

            Guid fieldGuid = new Guid(fieldId);

            SPField fld = null;

            if (web.Fields.ContainsField(fieldName))
            { 
                fld = web.Fields.GetField(fieldName);
            }
            
            XmlNodeReader nodeReader = new XmlNodeReader(fieldNode);
            nodeReader.Read();

            // Is this an update or insertion ?
            if (fld == null)
            {
                // insertion
                TraceHelper.TraceVerbose(
                        TraceSwitch,
                        "SiteColumnHelper",
                        "This Field '{0}' (passed in via the " + "XML), is not found - peforming an 'ADD'.",
                        fieldGuid.ToString());

                // add it
                AddNewSiteColumn(web, nodeReader);
            }
            else
            {
                TraceHelper.TraceVerbose(
                        TraceSwitch,
                        "SiteColumnHelper",
                        "This Field '{0}' (passed in via the " + "XML), already exists - peforming an 'UPDATE'.",
                        fieldGuid.ToString());

                // update the content type
                UpdateSiteColumn(ref fld, nodeReader);
            }

            TraceHelper.TraceMethodEnd(TraceSwitch, "SiteColumnHelper", "UpdateSaveSiteColumn");
        }

        /// <summary>
        /// Adds the new column.
        /// </summary>
        /// <param name="web">The web.</param>
        /// <param name="nodeReader">The node reader.</param>
        private static void AddNewSiteColumn(SPWeb web, XmlNodeReader nodeReader)
        {

            TraceHelper.TraceMethodStart(TraceSwitch, "SiteColumnHelper", "AddNewSiteColumn");

            if (nodeReader.LocalName == "Field")
            {
                string fieldsXml = nodeReader.ReadOuterXml();
                web.Fields.AddFieldAsXml(fieldsXml);
            }

            TraceHelper.TraceMethodEnd(TraceSwitch, "SiteColumnHelper", "AddNewSiteColumn");
        }

        /// <summary>
        /// Updates the site column.
        /// </summary>
        /// <param name="field">
        /// The field.
        /// </param>
        /// <param name="nodeReader">
        /// The node reader.
        /// </param>
        private static void UpdateSiteColumn(ref SPField field, XmlNodeReader nodeReader)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "SiteColumnHelper", "UpdateSiteColumn");

            if (nodeReader.LocalName == "Field")
            {
                string fieldsXml = nodeReader.ReadOuterXml();
                field.SchemaXml = fieldsXml;
                field.Update(true);
            }

            TraceHelper.TraceMethodEnd(TraceSwitch, "SiteColumnHelper", "UpdateSiteColumn");
        }

        /// <summary>
        /// Gets the field.
        /// </summary>
        /// <param name="web">The web.</param>
        /// <param name="fieldName">Field Name.</param>                
        /// <returns></returns>
        public static SPField GetField(SPWeb web,string fieldName)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "SiteColumnHelper", "GetField");
            ValidationHelper.VerifyObjectArgument(web, "web");
            ValidationHelper.VerifyStringArgument(fieldName, "fieldName");
            SPField field = null;

            if (web.Fields.ContainsField(fieldName))
            {
                field = web.Fields.GetField(fieldName);
            }
            
            TraceHelper.TraceMethodEnd(TraceSwitch, "SiteColumnHelper", "GetField");
            return field;
        }

        /// <summary>
        /// Adds the type of the site column to content.
        /// </summary>
        /// <param name="web">The web.</param>
        /// <param name="fieldName">Name of the field.</param>
        /// <param name="contentTypeName">Name of the content type.</param>
        /// <returns></returns>
        public static bool AddSiteColumnToContentType(SPWeb web,string fieldName,string contentTypeName)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "SiteColumnHelper", "AddSiteColumnToContentType");
            ValidationHelper.VerifyObjectArgument(web, "web");
            ValidationHelper.VerifyStringArgument(fieldName, "fieldName");
            ValidationHelper.VerifyStringArgument(contentTypeName, "contentTypeName");

            bool bSuccess = false;

            try
            {
                SPField field = GetField(web, fieldName);
            
                // TODO: Refactor the below lines.
                SPContentTypeId contentTypeId = SPContentTypeHelper.GetContentTypeIdFromName(web, contentTypeName);
                SPContentType contentType = web.ContentTypes[contentTypeId];

                if (contentType == null)
                {
                    TraceHelper.TraceError(TraceSwitch, "SiteColumnHelper", "Failed to get the content type : ",contentTypeName);
                    return false;
                }

                // Is the FieldLink in the collection?
                SPFieldLink fieldLink = contentType.FieldLinks[field.Id];
                if (fieldLink == null) // No, so add it.
                {
                    fieldLink = new SPFieldLink(field);
                    contentType.FieldLinks.Add(fieldLink);
                    contentType.Update();
                    bSuccess = true;
                }
                else
                {
                    TraceHelper.TraceWarning(TraceSwitch, "SiteColumnHelper", "Field {0} already exist in the content type {1}", fieldName, contentTypeName);
                }

            }
            catch (Exception ex)
            {
                TraceHelper.TraceError(TraceSwitch, "SiteColumnHelper", "Failed to add column to content type : {0}", ex);
            }

            TraceHelper.TraceMethodEnd(TraceSwitch, "SiteColumnHelper", "AddSiteColumnToContentType");
            return bSuccess;
        }

        

        /// <summary>
        /// Removes the type of the site column from content.
        /// </summary>
        /// <param name="web">The web.</param>
        /// <param name="fieldName">Name of the field.</param>
        /// <param name="contentTypeName">Name of the content type.</param>
        /// <returns></returns>
        public static bool RemoveSiteColumnFromContentType(SPWeb web, string fieldName, string contentTypeName)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "SiteColumnHelper", "RemoveSiteColumnFromContentType");
            ValidationHelper.VerifyObjectArgument(web, "web");
            ValidationHelper.VerifyStringArgument(fieldName, "fieldName");
            ValidationHelper.VerifyStringArgument(contentTypeName, "contentTypeName");

            bool bSuccess = false;

            try
            {
                SPField field = GetField(web, fieldName);

                // TODO: Refactor the below lines.
                SPContentTypeId contentTypeId = SPContentTypeHelper.GetContentTypeIdFromName(web, contentTypeName);
                SPContentType contentType = web.ContentTypes[contentTypeId];

                if (contentType == null)
                {
                    TraceHelper.TraceError(TraceSwitch, "SiteColumnHelper", "Failed to get the content type : {0}", contentTypeName);
                    return false;
                }

                // Is the FieldLink in the collection?
                SPFieldLink fieldLink = contentType.FieldLinks[field.Id];
                if (fieldLink == null) // No, so add it.
                {
                    TraceHelper.TraceWarning(TraceSwitch, "SiteColumnHelper", "Field {0} doesn't exist in the content type {1}", fieldName, contentTypeName);
                }
                else
                {
                    fieldLink = new SPFieldLink(field);
                    contentType.FieldLinks.Delete(fieldLink.Id);
                    contentType.Update();
                    bSuccess = true;   
                }
            }
            catch (Exception ex)
            {
                TraceHelper.TraceError(TraceSwitch, "SiteColumnHelper", "Failed to remove column from the content type", ex);
            }

            TraceHelper.TraceMethodEnd(TraceSwitch, "SiteColumnHelper", "RemoveSiteColumnFromContentType");
            return bSuccess;
        }

    }
}